﻿using System;
using System.IO;
using System.Linq;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Communication.Base;
using UtilZ.Dotnet.Ex.Communication.Net.Tcp;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Communication.Net.Web
{
    internal class WebSocketUnpacker : UnpackerAbs
    {
        /**********************************************************************
        * 帧协议(4byte-32bit)
        * 
        *  0               1               2               3
        *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        * +-+-+-+-+-------+-+-------------+-------------------------------+
        * |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
        * |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
        * |N|V|V|V|       |S|             |   (if payload len==126/127)   |
        * | |1|2|3|       |K|             |                               |
        * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
        * |     Extended payload length continued, if payload len == 127  |
        * + - - - - - - - - - - - - - - - +-------------------------------+
        * |                               |Masking-key, if MASK set to 1  |
        * +-------------------------------+-------------------------------+
        * | Masking-key (continued)       |          Payload Data         |
        * +-------------------------------- - - - - - - - - - - - - - - - +
        * :                     Payload Data continued ...                :
        * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
        * |                     Payload Data continued ...                |
        * +---------------------------------------------------------------+
        * 
        * 
        * 服务端收到没有添加掩码的数据帧以后，必须立即关闭连接。在这种情况下，服务端可以发送一个在7.4.1节定义的状态码为1002（协议错 误）的关闭帧。
        * 
        * RSV1，RSV2，RSV3: 每个1 bit
        * ​必须设置为0，除非扩展了非0值含义的扩展。如果收到了一个非0值但是没有扩展任何非0值的含义，接收终端必须断开WebSocket连接。
        * 
        * 
        * FIN: 1 bit​ 表示这是消息的最后一个片段。第一个片段也有可能是最后一个片段。
        * RSV1，RSV2，RSV3: 每个1 bit​ 必须设置为0，除非扩展了非0值含义的扩展。如果收到了一个非0值但是没有扩展任何非0值的含义，接收终端必须断开WebSocket连接。
        * 
        * Opcode: 4 bit
​        * 定义“有效负载数据”的解释。如果收到一个未知的操作码，接收终端必须断开WebSocket连接。下面的值是被定义过的。
​        * 0x0 表示一个持续帧
​        * 0x1 表示一个文本帧
​        * 0x2 表示一个二进制帧
​        * 0x3-7 预留给以后的非控制帧
​        * 0x8 表示一个连接关闭包
​        * 0x9 表示一个ping包
​        * 0xA 表示一个pong包
​        * 0xB-F 预留给以后的控制帧
        * 
        * Mask: 1 bit​ mask标志位，定义“有效负载数据”是否添加掩码。如果设置为1，那么掩码的键值存在于Masking-Key中，根据5.3节描述，这个一般用于解码“有效负载数据”。
        * 所有的从客户端发送到服务端的帧都需要设置这个bit位为1。
        * 
        * Payload length: 7 bits, 7+16 bits, or 7+64 bits ​以字节为单位的“有效负载数据”长度，如果值为0-125，那么就表示负载数据的长度。
        * 如果是126，那么接下来的2个bytes解释为16bit的无符 号整形作为负载数据的长度。如果是127，那么接下来的8个bytes解释为一个64bit的无符号整形（最高位的bit必须为0）
        * 作为负载数据的长度。 多字节长度量以网络字节顺序表示（译注：应该是指大端序和小端序）。在所有的示例中，长度值必须使用最小字节数来进行编码，
        * 例如：长度为124字节的字符 串不可用使用序列126,0,124进行编码。有效负载长度是指“扩展数据”+“应用数据”的长度。“扩展数据”的长度可能为0，
        * 那么有效负载长度就是 “应用数据”的长度。
        * 
        * Masking-Key: 0 or 4 bytes​ 所有从客户端发往服务端的数据帧都已经与一个包含在这一帧中的32 bit的掩码进行过了运算。如果mask标志位（1 bit）为1，那么这个字段存在，
        * 如果标志位为0，那么这个字段不存在。在5.3节中会介绍更多关于客户端到服务端增加掩码的信息。
        * 
        * Payload data: (x+y) bytes​ “有效负载数据”是指“扩展数据”和“应用数据”。
        * 
        * Extension data: x bytes​ 除非协商过扩展，否则“扩展数据”长度为0 bytes。在握手协议中，任何扩展都必须指定“扩展数据”的长度，这个长度如何进行计算，
        * 以及这个扩展如何使用。如果存在扩展，那么这个“扩展数据”包含在总的有效负载长度中。
        * 
        * Application data: y bytes​ 任意的“应用数据”，占用“扩展数据”后面的剩余所有字段。“应用数据”的长度等于有效负载长度减去“扩展应用”长度。
        * 基础数据帧协议通过ABNF进行了正式的定义。需要重点知道的是，这些数据都是二进制的，而不是ASCII字符。例如，长度为1 bit的字段的值为0x0/0x1代表的是一个值为0/1的
        * 单独的bit，而不是一整个字节（8 bit）来代表ASCII编码的字符“0”和“1”。一个长度为4 bit的范围是0x0-F的字段值代表的是4个bit，而不是字节（8 bit）对应的ASCII码的值。
        * 不要指定字符编码：“规则解析为一组最终的值，有时候是字符。在ABNF中，字符仅仅是一个非负的数字。在特定的上下文 中，会根据特定的值的映射（编码）编码集（例如ASCII）”。
        * 在这里，指定的编码类型是将每个字段编码为特定的bits数组的二进制编码的最终数据。
        * 
        * ws-frame =
        * frame-fin; 长度为1 bit
        * frame-rsv1; 长度为1 bit
        * frame-rsv2; 长度为1 bit
        * frame-rsv3; 长度为1 bit
        * frame-opcode; 长度为4 bit
        * frame-masked; 长度为1 bit
        * frame-payload-length; 长度为7或者7+16或者7+64 bit
        * [frame-masking-key]; 长度为32 bit
        * frame-payload-data; 长度为大于0的n*8 bit（其中n>0）
        * frame-fin =
        * 
        * 0x0，除了以下为1的情况
        * 0x1，最后一个消息帧
        * 长度为1 bit
        * frame-rsv1 =
        * 
        * 0x0/0x1，长度为1 bit，如果没有协商则必须为0
        * frame-rsv2 =
        * 
        * 0x0/0x1，长度为1 bit，如果没有协商则必须为0
        * frame-rsv3 =
        * 
        * 0x0/0x1，长度为1 bit，如果没有协商则必须为0
        * frame-opcode =
        * 
        * frame-opcode-non-control
        * frame-opcode-control
        * frame-opcode-cont
        * frame-opcode-non-control
        * 
        * 0x1，文本帧
        * 0x2，二进制帧
        * 0x3-7，保留给将来的非控制帧
        * 长度为4 bit
        * frame-opcode-control
        * 
        * 0x8，连接关闭
        * 0x9，ping帧
        * 0xA，pong帧
        * 0xB-F，保留给将来的控制帧
        * 长度为4 bit
        * frame-masked
        * 
        * 0x0，不添加掩码，没有frame-masking-key
        * 0x1，添加掩码，存在frame-masking-key
        * 长度为1 bit
        * frame-payload-length
        * 
        * 0x00-7D，长度为7 bit
        * 0x7E frame-payload-length-16，长度为7+16 bit
        * 0x7F frame-payload-length-63，长度为7+64 bit
        * frame-payload-length-16
        * 
        * 0x0000-FFFF，长度为16 bit
        * frame-payload-length-63
        * 
        * 0x0000000000000000-7FFFFFFFFFFFFFFF，长度为64 bit
        * frame-masking-key
        * 
        * 4(0x00-FF)，当frame-mask为1时存在，长度为32 bit
        * frame-payload-data
        * 
        * frame-masked-extension-data frame-masked-application-data，当frame-masked为1时
        * frame-unmasked-extension-data frame-unmasked-application-data，当frame-masked为0时
        * frame-masked-extension-data
        * 
        * *(0x00-FF)，保留给将来的扩展，长度为n*8，其中n>0
        * frame-masked-application-data
        * 
        * *(0x00-FF)，长度为n*8，其中n>0
        * frame-unmasked-extension-data
        * 
        * *(0x00-FF)，保留给将来的扩展，长度为n*8，其中n>0
        * frame-unmasked-application-data
        * 
        * (0x00-FF)，长度为n*8，其中n>0
        **********************************************************************/

        private const int _HEAD_MIN_SIZE = 6;

        private byte[] _tmpBuffer = null;

        private string _filePath = null;
        private FileStream _fileStream = null;
        private readonly MemoryStream _dataStream = new MemoryStream();

        private readonly byte[] _maskingKey = new byte[4];

        public WebSocketUnpacker()
        {

        }

        /// <summary>
        /// 对数据进行拆包
        /// </summary>
        /// <param name="unpackerPara">分包参数</param>
        /// <returns>拆包成功的数据长度</returns>
        protected override int PrimitiveUnPackge(IUnpackerPara unpackerPara)
        {
            var np = (NetTcpUnpackerPara)unpackerPara;
            if (np.DataLength == 0)
            {
                return 0;
            }

            var reader = np.Reader;
            long parseBeginPosition = reader.BaseStream.Position;
            long currentParseBeginPosition = parseBeginPosition;
            long parseEndPosition = parseBeginPosition + np.DataLength;
            int parseDataLength = 0;

            //至少需要6个字节才够头
            if (parseEndPosition - currentParseBeginPosition < _HEAD_MIN_SIZE)
            {
                return parseDataLength;
            }

            WebSocketDataOutputArgs args;

            while (reader.BaseStream.Position < parseEndPosition &&
                parseEndPosition - reader.BaseStream.Position >= _HEAD_MIN_SIZE)
            {
                //注意：不像这篇文档中的其他章节内容，在这节中的ABNF是对bit组进行操作。
                //每一个bit组的长度是在评论中展示的。在线上编码 时，最高位的bit是在ABNF最左边的
                //所以按字节读取,每个字节再8位小端处理
                byte h1 = reader.ReadByte();
                byte h2 = reader.ReadByte();
                byte[] headers = GetBitValues(h1, h2);

                //​必须设置为0，除非扩展了非0值含义的扩展。如果收到了一个非0值但是没有扩展任何非0值的含义，接收终端必须断开WebSocket连接。
                for (int i = 1; i < 4; i++)
                {
                    if (headers[i] != 0)
                    {
                        base.OnRaiseOnReceivedData(np.Client, null);
                        parseDataLength = (int)(reader.BaseStream.Position - parseBeginPosition);
                        return parseDataLength;
                    }
                }

                //消息类型
                string opcodeStr = string.Join(string.Empty, headers.Skip(4).Take(4));
                byte opcode = Convert.ToByte(opcodeStr, 2);

                //有效负载数据长度
                string payloadLlenStr = string.Join(string.Empty, headers.Skip(9).Take(7));
                ulong payloadLen = Convert.ToUInt32(payloadLlenStr, 2);
                //以字节为单位的“有效负载数据”长度，如果值为0-125，那么就表示负载数据的长度。
                //如果是126，那么接下来的2个bytes解释为16bit的无符 号整形作为负载数据的长度。
                //如果是127，那么接下来的8个bytes解释为一个64bit的无符号整形（最高位的bit必须为0）作为负载数据的长度。 
                //多字节长度量以网络字节顺序表示（译注：应该是指大端序和小端序）。
                if (payloadLen >= 0 && payloadLen <= 125)
                {

                }
                else if (payloadLen == 126)
                {
                    payloadLen = reader.ReadUInt16();
                }
                else if (payloadLen == 127)
                {
                    payloadLen = reader.ReadUInt64();
                }
                else
                {
                    ZLoger.Warn($"未知的载荷数据长度:{payloadLen},关闭websocket连接");
                    base.OnRaiseOnReceivedData(np.Client, null);
                    parseDataLength = (int)(reader.BaseStream.Position - parseBeginPosition);
                    break;
                }

                //服务端收到没有添加掩码的数据帧以后，必须立即关闭连接
                if (headers[8] == 0)
                {
                    base.OnRaiseOnReceivedData(np.Client, null);
                    parseDataLength = (int)(reader.BaseStream.Position - parseBeginPosition);
                    break;
                }

                //如果mask标志位（1 bit）为1，那么这个字段存在，如果标志位为0，那么这个字段不存在
                //var maskingKey = reader.ReadInt32();
                for (int i = 0; i < this._maskingKey.Length; i++)
                {
                    this._maskingKey[i] = reader.ReadByte();
                }

                //载荷数据=扩展数据(数据长度在websocket建立握手请求头中的子版本协议规则)+应用数据
                var availableDataLen = (ulong)(parseEndPosition - reader.BaseStream.Position);
                int dataLen;
                if (availableDataLen < payloadLen)
                {
                    dataLen = (int)availableDataLen;
                    if (this._tmpBuffer == null || (ulong)this._tmpBuffer.Length < availableDataLen)
                    {
                        this._tmpBuffer = new byte[availableDataLen];
                    }
                }
                else
                {
                    dataLen = (int)payloadLen;
                    if (this._tmpBuffer == null || (ulong)this._tmpBuffer.Length < payloadLen)
                    {
                        this._tmpBuffer = new byte[payloadLen];
                    }
                }
                reader.Read(this._tmpBuffer, 0, dataLen);
                currentParseBeginPosition = reader.BaseStream.Position;

                //反掩码
                this.DeMaskingKey(this._tmpBuffer, dataLen, this._maskingKey);

                if (HttpOpcodes.IsControlMessage(opcode))
                {
                    //在分片传输数据过程中,因为分片数可能很多,要传输很久,所以允许控制消息插队,且优先级更高.
                    //因此先判断消息是否是控制消息后,如果是直接输出,之后分片数据继续接收
                    byte[] data = this._dataStream.GetBuffer().Take((int)this._dataStream.Position).ToArray();
                    args = new WebSocketDataOutputArgs(np.LinkInfo, new DataSource(data, 0, data.Length), opcode);
                    base.OnRaiseOnReceivedData(np.Client, args);
                }
                else
                {
                    if (payloadLen > 104857600)
                    {
                        //大于100MB,写文件(104857600 = 1024 * 1024 * 100)
                        if (this._fileStream == null)
                        {
                            this._filePath = $"{GUIDEx.GetGUIDHashCode()}.dat";
                            this._fileStream = new FileStream(this._filePath, FileMode.Create, FileAccess.Write);
                        }

                        this._fileStream.Write(this._tmpBuffer, 0, dataLen);
                        this._fileStream.Flush();
                    }
                    else
                    {
                        this._dataStream.Write(this._tmpBuffer, 0, dataLen);
                        this._dataStream.Flush();
                    }
                }

                //0还有后续帧,1最后一帧
                if (headers[0] == 1)
                {
                    if (this._fileStream != null)
                    {
                        this._fileStream.Close();
                        args = new WebSocketDataOutputArgs(np.LinkInfo, new DataSource(this._filePath, 0, this._fileStream.Length), opcode);
                        this._fileStream = null;
                        this._filePath = null;
                    }
                    else
                    {
                        byte[] data = this._dataStream.GetBuffer().Take((int)this._dataStream.Position).ToArray();
                        this._dataStream.Position = 0;
                        args = new WebSocketDataOutputArgs(np.LinkInfo, new DataSource(data, 0, data.Length), opcode);
                    }

                    base.OnRaiseOnReceivedData(np.Client, args);
                }
                else
                {
                    //还有后续帧,不输出,等待接收完成后再输出                    
                }

            }

            parseDataLength = (int)(currentParseBeginPosition - parseBeginPosition);

            if (reader.BaseStream.Position != currentParseBeginPosition)
            {
                reader.BaseStream.Position = currentParseBeginPosition;
            }

            return parseDataLength;
        }


        private void DeMaskingKey(byte[] buffer, int lenth, byte[] maskingKey)
        {
            if (buffer == null || buffer.Length == 0)
            {
                return;
            }

            byte v, m;
            for (int i = 0; i < lenth; i++)
            {
                v = buffer[i];
                m = maskingKey[i % maskingKey.Length];
                buffer[i] = (byte)(v ^ m);
            }
        }



        private byte[] GetBitValues(byte h1, byte h2)
        {
            byte[] b1 = ConvertEx.ByteToBits(h1);
            byte[] b2 = ConvertEx.ByteToBits(h2);
            byte[] bitValueArr = new byte[16];
            Array.Copy(b1, 0, bitValueArr, 0, b1.Length);
            Array.Copy(b2, 0, bitValueArr, b1.Length, b2.Length);
            return bitValueArr;
        }


        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="isDisposing"></param>
        protected override void Dispose(bool isDisposing)
        {
            base.Dispose(isDisposing);

            this._dataStream.Dispose();
            if (this._fileStream != null)
            {
                this._fileStream.Close();
                this._fileStream.Dispose();
                this._fileStream = null;
            }
        }
    }
}
